/*
    Seminar Computergrafik SS2012
    Praktikum
    Fachhochschule Münster
    Fachbereich Elektrotechnik und Informatik    
    
    Autoren: Alexander Kuhn & Mark Ruffer
*/

/* WebGL Objekt */
var gl;

/* Das Canvas Zeichenfeld */
var canvas;

/* Texturen */
var texture;

var vertexTextureCoordBuffer;

/* Speicher für Vertex Positionen */
var vBufferBody;
var vBufferLeftLeg;
var vBufferRightLeg;
var vBufferLeftArm;
var vBufferRightArm;
var vBufferHead;

/* Speicher für Farbinformationen */
var cBufferBody;
var cBufferLeftLeg;
var cBufferRightLeg;
var cBufferLeftArm;
var cBufferRightArm;
var cBufferHead;

var cBufferStandardCube = [
    [0.5,   0.5,    0.5,    1.0],
    [0.5,   0.5,    0.5,    1.0],
    [0.5,   0.5,    0.5,    1.0],
    [0.5,   0.5,    0.5,    1.0],
    [0.5,   0.5,    0.5,    1.0],
    [0.5,   0.5,    0.5,    1.0]
];

/* Speicher für Indexinformationen */
var iBufferBody;
var iBufferLeftLeg;
var iBufferRightLeg;
var iBufferLeftArm;
var iBufferRightArm;
var iBufferHead;

var iBufferStandardCube = [
    0, 1, 2,      0, 2, 3,     /* front */ 
    4, 5, 6,      4, 6, 7,     /* hinten */
    8, 9, 10,     8, 10, 11,   /* oben */  
    12, 13, 14,   12, 14, 15,  /* unten */ 
    16, 17, 18,   16, 18, 19,  /* rechts */
    20, 21, 22,   20, 22, 23   /* links */ 
];

/* Matrizen */
var pMatrix = mat4.create();      /* Projektionsmatrix */
var mvMatrix = mat4.create();     /* Model-View-Matrix */

/* Stacks */
var mvMatrixStack = [];           /* Stack für die mvMatrix */

/* Shaderprogramme */
var shaderProgram;

/* Animationsvariablen */
var xRot = 0;
var xSpeed = 0;
var xTrans = 0;
var yRot = 0;
var ySpeed = 0;
var yTrans = 0;
var z = -100.0;
var rRobot = 0;                    /* rotation des würfels */
var rLegs = 0;
var rArms = 0;
var rHead = 0;
var rTorso = 0;
var lastTime = 0;
var plusitLegs = true;
var plusitArms = true;
var plusitHead = true;
var plusitTorso = true;

/**
 * Setzt den Drehwinkel so, dass 90° bzw 45° pro Sekunde gedreht werden. Es wird
 * nicht in jedem Durchlauf ein fester Wert gedreht, sondern Abhängig von der ver-
 * gangenen Zeit, sodass die Animation pro Zeit fest rotiert.  
 */ 
function animate(){
    var timeNow, elapsed, interm;
    timeNow = new Date().getTime();
    if(lastTime != 0){
        elapsed = timeNow - lastTime;
        rRobot += (100 * elapsed)/1000.0;		// Drehung des gesamten Robos um eigene Achse
        
        interm = (20 * elapsed)/1000.0;	//Angabe der Gradzahl-Drehung pro Sekunde
        if(plusitLegs){
            rLegs += interm;              
        } else {
            rLegs -= interm;
        }
        if(rLegs >= 33.0){
        	plusitLegs = false;
        } else if (rLegs <= -33.0){
        	plusitLegs = true;
        }
        
        // Arms
        interm = (0 * elapsed)/1000.0;
        if(plusitArms){
        	rArms += interm;
        } else {
        	rArms -= interm;
        }
        if(rArms >= 75.0){
        	plusitArms = false;
        } else if(rArms <= -25.0) {
        	plusitArms = true;
        }
        
        // Head
        interm = (0 * elapsed)/1000.0;
        if(plusitHead){
        	rHead += interm;
        } else {
        	rHead -= interm;
        }
        if(rHead >= 45.0){
        	plusitHead = false;
        } else if(rHead <= -45.0){
        	plusitHead = true;
        }
        
        //  Torso
        interm = (0 * elapsed)/1000.0;
        if(plusitTorso) rTorso += interm;
        else            rTorso -= interm;
        if(rTorso >= 0.0)   plusitTorso = false;
        else if(rTorso <= -45.0) plusitTorso = true;
    }
    lastTime = timeNow;
}

/**
 * Initialisiere das WebGL Objekt aus dem Canvas
 * @param canvas Die HTML Zeichenfläche
 */  
function initGL(canvas){
    try {
        gl = canvas.getContext("experimental-webgl");
        gl.viewportWidth = canvas.width;
        gl.viewportHeight = canvas.height;                
    }
    catch(e){
        window.alert("Fehler: " + e.message);
    }
                                   
    if(!gl){
        window.alert("WebGL konnte nicht initialisiert werden.");
    }
}

/**
 * Erzeugt einen Shader
 * 
 * @param gl the WebGL Context  
 * @param shader_type Typ des Shaders (gl.VERTEX_SHADER, gl.FRAGMENT_SHADER)
 * @param shader_source String Variable mit dem Shader Quelltext
 * @return Erzeugter Shader bzw. null bei Fehlern   
 */   
function getShader(gl, shader_type, shader_source) {
    var shader;

    // Shader erzeugen 
    shader = gl.createShader(shader_type);

    // Shaderquelltext laden und kompilieren
    gl.shaderSource(shader, shader_source);
    gl.compileShader(shader);

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        window.alert("Compiling Error: "+gl.getShaderInfoLog(shader));
        return null;
    }

    return shader;
}

/**
 * Initialisiert die Shader
 */ 

function initShaders(){
    var fragmentShader, vertexShader;
    
    fragmentShader = getShader(gl, gl.FRAGMENT_SHADER, shaderprogram_fragmentshader);
    vertexShader = getShader(gl, gl.VERTEX_SHADER, shaderprogram_vertexshader);

    /* das shaderProgram kann je einen fragment und vertex shader beherbergen */
    shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, vertexShader);
    gl.attachShader(shaderProgram, fragmentShader);
    gl.linkProgram(shaderProgram);

    if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
      window.alert("Could not initialise shaders");
    }

    gl.useProgram(shaderProgram);

    /* 
        die position eines vertex wird als attribut an den shader geliefert.
        dazu wird die speicheradresse dieser shader variablen als zusätzliche
        eigenschaft an das shaderprogram angehangen.
    */   
    shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
    gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
    
    /* adresse für attribut aVertexColor speichern */
    /* KEINE FARBEN MEHR -> TEXTUREN
    shaderProgram.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor");
    gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute);
    */
    
    /* adresse für attribut aTextureCoord speichern */
    shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");
    gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);

    /* adressen der matrizen speichern */    
    shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
    shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
}


//Hashtable zur Verwendung um Tasten-Events abzuarbeiten
var currentlyPressedKeys = {};

function handleKeyDown(event){
	currentlyPressedKeys[event.keyCode] = true;
	
	if(String.fromCharCode(event.keyCode) == "F"){
		filter +=1;
		if (filter == 3){
			filter = 0;
		}
	}
}

function handleKeyUp(event){
	currentlyPressedKeys[event.keyCode] = false;
}


function handleKeys(){
	if(currentlyPressedKeys[33]){
		// Page Up
		z -= 1.0;
	}
	if(currentlyPressedKeys[34]){
		// Page Down
		z += 1.0;
	}
	if(currentlyPressedKeys[37]){
		// Left cursor key
		yRot -= 1;
	}
	if(currentlyPressedKeys[39]){
		// Right cursor key
		yRot += 1;
	}
	if(currentlyPressedKeys[38]){
		// Up cursor key
		xRot -= 1;
	}
	if(currentlyPressedKeys[40]){
		// Down cursor key
		xRot += 1;
	}
	if(currentlyPressedKeys[65]){
		// Left translation, key "a"
		xTrans += 1;
	}
	if(currentlyPressedKeys[68]){
		// Right translation, key "d"
		xTrans -= 1;
	}
	if(currentlyPressedKeys[87]){
		// Left translation, key "w"
		yTrans += 1;
	}
	if(currentlyPressedKeys[83]){
		// Right translation, key "s"
		yTrans -= 1;
	}
	if(currentlyPressedKeys[48] || currentlyPressedKeys[96]){
		// Reset Position
		xRot = 0;
		yRot = 0;
	}
}



/**
 * Initializes the given buffer.
 * - create WebGL Buffer
 * - fill in the data
 * - set itemSize (count of numbers for one item)
 * - set numItems (number of items)
 * 
 * @param gl the WebGL context
 * @param bufferType the type of the buffer (gl.ARRAY_BUFFER etc)
 * @param values an Array containing the values of the buffers items
 * @param drawType the WebGL drawtype (gl.STATIC_DRAW etc)
 * @param itemSize
 * @param numItems
 * @param arrayType (0:Float32Array, 1:Uint16Array) 
 * @return a filled and extended buffer or null in error    
 */            
function initBuffer(gl, bufferType, values, drawType, items, itemSize, arrayType){
    var buffer, array;

    switch(arrayType){
        case 0: array = new Float32Array(values); break;
        case 1: array = new Uint16Array(values); break;
        default: 
            window.alert("Wrong Array Type! Returning null."); 
            return null;
    }    
    buffer = gl.createBuffer();
    gl.bindBuffer(bufferType, buffer);
    gl.bufferData(bufferType, array, drawType);
    buffer.itemSize = itemSize;
    buffer.numItems = items;
    return buffer;
}


/**
 * Initialisiert die Speicherblöcke
 */ 
function initBuffers(){
    var vertices, colors;
    var vertexIndices;

    var textureCoords = [
      // Front face
      0.0, 0.0,
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,

      // Back face
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,
      0.0, 0.0,

      // Top face
      0.0, 1.0,
      0.0, 0.0,
      1.0, 0.0,
      1.0, 1.0,

      // Bottom face
      1.0, 1.0,
      0.0, 1.0,
      0.0, 0.0,
      1.0, 0.0,

      // Right face
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,
      0.0, 0.0,

      // Left face
      0.0, 0.0,
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0
    ];
    
    vertexTextureCoordBuffer = initBuffer(gl, gl.ARRAY_BUFFER, textureCoords, gl.STATIC_DRAW, 24, 2, 0);
    
    /* ***** BODY ***** */
    			//cubeArray(x, y, z, width, height, depth)
    vertices = cubeArray(-10.0, -10.0, 0.0, 30.0, 10.0, 6.0);

    vBufferBody = initBuffer(gl, gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW, 24, 3, 0);
    cBufferBody = initBuffer(gl, gl.ARRAY_BUFFER, unpackColors(cBufferStandardCube), gl.STATIC_DRAW, 24, 4, 0);
    iBufferBody = initBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, iBufferStandardCube, gl.STATIC_DRAW, 36, 1, 1);
    
    /* ***** LEFT LEG ***** */
    vertices = cubeArray(15.0, -20.0, 4.0, 2.0, 16.0, 2.0);

    vBufferLeftLeg = initBuffer(gl, gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW, 24, 3, 0);
    cBufferLeftLeg = initBuffer(gl, gl.ARRAY_BUFFER, unpackColors(cBufferStandardCube), gl.STATIC_DRAW, 24, 4, 0);
    iBufferLeftLeg = initBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, iBufferStandardCube, gl.STATIC_DRAW, 36, 1, 1);
    
    /* ***** RIGHT LEG ***** */
    vertices = cubeArray(15.0, -20.0, -8.0, 2.0, 16.0, 2.0);
    
    vBufferRightLeg = initBuffer(gl, gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW, 24, 3, 0);
    cBufferRightLeg = initBuffer(gl, gl.ARRAY_BUFFER, unpackColors(cBufferStandardCube), gl.STATIC_DRAW, 24, 4, 0);
    iBufferRightLeg = initBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, iBufferStandardCube, gl.STATIC_DRAW, 36, 1, 1);
    
    /* ***** LEFT ARM ***** */
    vertices = cubeArray(-5.0, -20.0, 4.0, 2.0, 16.0, 2.0);
    
    vBufferLeftArm = initBuffer(gl, gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW, 24, 3, 0);
    cBufferLeftArm = initBuffer(gl, gl.ARRAY_BUFFER, unpackColors(cBufferStandardCube), gl.STATIC_DRAW, 24, 4, 0);
    iBufferLeftArm = initBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, iBufferStandardCube, gl.STATIC_DRAW, 36, 1, 1);
    
    /* ***** RIGHT ARM ***** */
    vertices = cubeArray(-5.0, -20.0, -8.0, 2.0, 16.0, 2.0);
    
    vBufferRightArm = initBuffer(gl, gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW, 24, 3, 0);
    cBufferRightArm = initBuffer(gl, gl.ARRAY_BUFFER, unpackColors(cBufferStandardCube), gl.STATIC_DRAW, 24, 4, 0);
    iBufferRightArm = initBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, iBufferStandardCube, gl.STATIC_DRAW, 36, 1, 1);
    
    /* ***** HEAD ***** */
    vertices = cubeArray(-20.0, 5.0, 0.0, 10.0, 10.0, 6.0);
    
    vBufferHead = initBuffer(gl, gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW, 24, 3, 0);
    cBufferHead = initBuffer(gl, gl.ARRAY_BUFFER, unpackColors(cBufferStandardCube), gl.STATIC_DRAW, 24, 4, 0);
    iBufferHead = initBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, iBufferStandardCube, gl.STATIC_DRAW, 36, 1, 1);        
}

/**
 * Lädt die entsprechenden Speichereinheiten und führt drawElements aus
 * @param gl das WebGL Objekt
 * @param shadProg das ShaderProgram
 * @param drawElementType 
 * @param vBuffer
 * @param vBufferArrayType
 * @param vBufferNumberType
 * @param cBuffer
 * @param cBufferArrayType
 * @param cBufferNumberType
 * @param iBuffer
 * @param iBufferArrayType
 * @param iBufferNumberType
 */         
function bindAndDraw(gl, shadProg, drawElementType, vBuffer, vBufferArrayType, vBufferNumberType, cBuffer, cBufferArrayType, cBufferNumberType, iBuffer, iBufferArrayType, iBufferNumberType){
    gl.bindBuffer(vBufferArrayType, vBuffer);
    gl.vertexAttribPointer(shadProg.vertexPositionAttribute, vBuffer.itemSize, vBufferNumberType, false, 0, 0);
    /* KEINE FARBEN MEHR -> TEXTUREN
    gl.bindBuffer(cBufferArrayType, cBuffer);
    gl.vertexAttribPointer(shadProg.vertexColorAttribute, cBuffer.itemSize, cBufferNumberType, false, 0, 0);
    */
    // TODO: flexibler gestalten
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexTextureCoordBuffer);
    gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, vertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.uniform1i(shaderProgram.samplerUniform, 0);
    gl.bindBuffer(iBufferArrayType, iBuffer);
    setMatrixUniforms();
    gl.drawElements(drawElementType, iBuffer.numItems, iBufferNumberType, 0);
}

/**
 * Führt die Berechnungen auf der Grafikkarte durch und bringt die erzeugte Grafik auf
 * die Zeichenfläche
 */  
function drawScene(){
    /* Viewport definieren über der Größe des Canvas */
    gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
    /* Zeichenfläche leeren */
    gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
    
    /*
        perspektive festlegen:
            45° blickwinkel
            seitenverhältnis des canvas
            nur elemente, die weiter weg sind als 0.1 einheiten
            nur elemente, die näher sind als 100 einheiten
            
    */
    mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 200.0, pMatrix);
    
    /* Einheitsmatrix erzeugen */    
    mat4.identity(mvMatrix);
    
    /* gesamtes Bild verschieben */
    //mat4.translate(mvMatrix, [0.0, 5.0, z]);
    mat4.translate(mvMatrix, [xTrans, yTrans, z]);

    /* Rotation gemäß aktueller Drehposition */    
    mat4.rotate(mvMatrix, degToRad(xRot), [1, 0, 0]);
    mat4.rotate(mvMatrix, degToRad(yRot), [0, 1, 0]);

    /* Laden und Zeichnen der Objekte */
   

    /* ***** BODY ***** */
    mvPushMatrix();
	    mat4.translate(mvMatrix, [0.0, -10.0, 0.0]);
	    mat4.rotate(mvMatrix, degToRad(rTorso), [1, 0, 0]);
	    mat4.translate(mvMatrix, [0.0, 10.0, 0.0]);
	
	    bindAndDraw(gl, shaderProgram, gl.TRIANGLES, vBufferBody, gl.ARRAY_BUFFER, gl.FLOAT, cBufferBody, gl.ARRAY_BUFFER, gl.FLOAT, iBufferBody, gl.ELEMENT_ARRAY_BUFFER, gl.UNSIGNED_SHORT);
	    
	    /* ***** ARMs ***** */
	    mvPushMatrix();    
	    mat4.translate(mvMatrix, [0.0, 6.0, 0.0]);
	    mat4.rotate(mvMatrix, degToRad(rArms), [0, 0, 1]);
	    mat4.translate(mvMatrix, [0.0, -6.0, 0.0]);
	    bindAndDraw(gl, shaderProgram, gl.TRIANGLES, vBufferLeftArm, gl.ARRAY_BUFFER, gl.FLOAT, cBufferLeftArm, gl.ARRAY_BUFFER, gl.FLOAT, iBufferLeftArm, gl.ELEMENT_ARRAY_BUFFER, gl.UNSIGNED_SHORT);
	    bindAndDraw(gl, shaderProgram, gl.TRIANGLES, vBufferRightArm, gl.ARRAY_BUFFER, gl.FLOAT, cBufferRightArm, gl.ARRAY_BUFFER, gl.FLOAT, iBufferRightArm, gl.ELEMENT_ARRAY_BUFFER, gl.UNSIGNED_SHORT);
	    mvPopMatrix();
	    
	    /* ***** HEAD ***** */
	    mvPushMatrix();
	    mat4.rotate(mvMatrix, degToRad(rHead), [0, 1, 0]);
	    bindAndDraw(gl, shaderProgram, gl.TRIANGLES, vBufferHead, gl.ARRAY_BUFFER, gl.FLOAT, cBufferHead, gl.ARRAY_BUFFER, gl.FLOAT, iBufferHead, gl.ELEMENT_ARRAY_BUFFER, gl.UNSIGNED_SHORT);
	    mvPopMatrix();
        
    mvPopMatrix();
    
    /* ***** LEGs ***** */
    mvPushMatrix();
    mat4.translate(mvMatrix, [0.0, -6.0, 0.0]);
    mat4.rotate(mvMatrix, degToRad(rLegs), [0, 0, 1]);
    mat4.translate(mvMatrix, [0.0, 6.0, 0.0]);    
    bindAndDraw(gl, shaderProgram, gl.TRIANGLES, vBufferLeftLeg, gl.ARRAY_BUFFER, gl.FLOAT, cBufferLeftLeg, gl.ARRAY_BUFFER, gl.FLOAT, iBufferLeftLeg, gl.ELEMENT_ARRAY_BUFFER, gl.UNSIGNED_SHORT);
    mvPopMatrix();
    mvPushMatrix();
    mat4.translate(mvMatrix, [0.0, -18.0, 0.0]);
    mat4.rotate(mvMatrix, degToRad(-rLegs), [0, 0, 1]);
    mat4.translate(mvMatrix, [0.0, 18.0, 0.0]);
    bindAndDraw(gl, shaderProgram, gl.TRIANGLES, vBufferRightLeg, gl.ARRAY_BUFFER, gl.FLOAT, cBufferRightLeg, gl.ARRAY_BUFFER, gl.FLOAT, iBufferRightLeg, gl.ELEMENT_ARRAY_BUFFER, gl.UNSIGNED_SHORT);
    mvPopMatrix();
    
}

/* QUICK AND DIRTY FPS MEASUREMENT */
var fps_lasttime = 0;
var fps_median = [];
var fps_counter = 0;
function measureFPS(){
    var now, val, sum, i;
    
    now = new Date().getTime();
    if(fps_lasttime!=0){
        val = 1000/(now - fps_lasttime);
        fps_median[fps_counter] = val;
        fps_counter = (fps_counter+1) % 100;
        sum = 0;
        for(i=0; i<100; i++) {
            sum += fps_median[i];
        }
        if(!isNaN(sum/100)) document.getElementById("FPS").value = Math.round(sum/100);
        else document.getElementById("FPS").value = "wait...";
        
    }
    fps_lasttime = now;    
}
/* QUICK AND DIRTY FPS MEASUREMENT */

var mcheck=false;
function tick(){
    /* QUICK AND DIRTY FPS MEASUREMENT */
    if( document.getElementById("FPS_CHECK").checked == true ){
        measureFPS();
        mcheck=true;
    }
    else if(mcheck==true){
        mcheck=false;
        document.getElementById("FPS_CHECK").checked = false;
        document.getElementById("FPS").value = "";
    }
    /* QUICK AND DIRTY FPS MEASUREMENT */
    requestAnimFrame(tick);
    handleKeys();
    drawScene();
    animate();
}

function handleLoadedTexture(texture){
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.bindTexture(gl.TEXTURE_2D, null);
}

function initTexture(){
    texture = gl.createTexture();
    texture.image = new Image();
    texture.image.onload = function(){
        handleLoadedTexture(texture);
    }
    texture.image.src = "texture.gif";
}

/**
 * Hauptroutine des Programms
 */                 
function main(){
    canvas = document.getElementById("webgl_canvas");
    initGL(canvas);
    initShaders();
    initBuffers();
    initTexture();
    
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.enable(gl.DEPTH_TEST);
    
	// Funktionen zum handeln der Tastendruecke
	document.onkeydown = handleKeyDown;
	document.onkeyup = handleKeyUp;
    
    tick();    
}
